1 module myers_diff; 2 3 4 import std.stdio; 5 import std.typecons; 6 import std.math; 7 import std.array; 8 import std.conv; 9 template MyersDiff(T) 10 { 11 private: 12 class V 13 { 14 private: 15 long[] i_; 16 long start_; 17 long end_; 18 public: 19 this(long start, long end) 20 { 21 i_.length = end - start + 1; 22 start_ = start; 23 end_ = end; 24 } 25 long* at(long idx) 26 { 27 return &i_[idx - start_]; 28 } 29 }; 30 private: 31 Tuple!(long, long, long, long, long) FindMiddleSnake(T[] a, T[] b) 32 { 33 long N = a.length; 34 long M = b.length; 35 long delta = N - M; 36 long MAX = (M + N) * 2; 37 auto fv = new V(-MAX, MAX); 38 auto rv = new V(-MAX, MAX); 39 long x, y; 40 (*fv.at(1)) = 0; 41 (*rv.at(delta + 1)) = N + 1; 42 for (long D = 0; D <= ceil((M + N) / 2.0); D++) 43 { 44 for (long k = -D; k <= D; k += 2) 45 { 46 if (k == -D || (k != D && (*fv.at(k - 1)) < (*fv.at(k + 1)))) 47 { 48 x = (*fv.at(k + 1)); 49 } 50 else 51 { 52 x = (*fv.at(k - 1)) + 1; 53 } 54 y = x - k; 55 while (x < N && y < M && a[x] == b[y]) 56 { 57 ++x; 58 ++y; 59 } 60 (*fv.at(k)) = x; 61 if (delta % 2 != 0 && k >= delta - (D - 1) && k <= delta + D - 1) 62 { 63 if ((*fv.at(k)) >= (*rv.at(k))) 64 { 65 return tuple((*rv.at(k)), (*rv.at(k)) - k, x, y, 2 * D - 1); 66 } 67 } 68 } 69 70 for (long k = -D + delta; k <= D + delta; k += 2) 71 { 72 if (k == -D + delta || (k != D + delta && (*rv.at(k - 1)) >= (*rv.at(k + 1)))) 73 { 74 x = (*rv.at(k + 1)) - 1; 75 } 76 else 77 { 78 x = (*rv.at(k - 1)); 79 } 80 y = x - k; 81 while (x > 0 && y > 0 && a[x - 1] == b[y - 1]) 82 { 83 x -= 1; 84 y -= 1; 85 } 86 (*rv.at(k)) = x; 87 if (delta % 2 == 0 && k >= -D && k <= D) 88 { 89 if ((*fv.at(k)) >= (*rv.at(k))) 90 { 91 return tuple(x, y, (*fv.at(k)), (*fv.at(k)) - k, 2 * D); 92 } 93 } 94 } 95 } 96 return tuple(long(0), long(0), long(0), long(0), long(0)); 97 } 98 public: 99 enum EditType 100 { 101 Add = 0, 102 Remove = 1 103 }; 104 struct DiffResult 105 { 106 long pos; 107 EditType type; 108 T str; 109 }; 110 DiffResult[] getDiff(T[] a, T[] b, long offset = 0) 111 { 112 DiffResult[] rt; 113 long N = a.length; 114 long M = b.length; 115 long Min = N < M ? N : M; 116 long j = 0; 117 for (;j < Min && a[0] == b[0]; ++j) 118 { 119 a = a[1..$]; 120 b = b[1..$]; 121 --N; 122 --M; 123 } 124 offset += j; 125 while (N > 0 && M > 0 && a[N - 1] == b[M - 1]) 126 { 127 --N; 128 --M; 129 } 130 if (N > 0 && M > 0) 131 { 132 auto t = FindMiddleSnake(a, b); 133 rt ~= getDiff(a[0..t[0]], b[0..t[1]], offset); 134 rt ~= getDiff(a[t[2]..$], b[t[3]..$], offset + t[2]); 135 } 136 else if (N > 0) 137 { 138 for (long i = 0; i < N; i++) 139 { 140 DiffResult itm; 141 itm.pos = i + offset; 142 itm.type = EditType.Remove; 143 itm.str = a[i]; 144 rt ~= itm; 145 } 146 } 147 else if (M > 0) 148 { 149 for (long i = 0; i < M; i++) 150 { 151 DiffResult itm; 152 itm.pos = N + offset; 153 itm.type = EditType.Add; 154 itm.str = b[i]; 155 rt ~= itm; 156 } 157 } 158 return rt; 159 } 160 private: 161 string formatOne(DiffResult r, string format, string strAdd, string strDelete) 162 { 163 string op = strAdd; 164 if (r.type == EditType.Remove) 165 { 166 op = strDelete; 167 } 168 string pos = to!string(r.pos); 169 string contest = to!string(r.str); 170 return format.replace("%pos%", pos).replace("%type%", op).replace("%content%", contest); 171 } 172 public: 173 string formatResult(DiffResult[] results, string format, string strAdd, string strDelete, size_t max_line = size_t.max) 174 { 175 string rt; 176 auto len = results.length < max_line ? results.length : max_line; 177 auto rest = results.length - len; 178 for (size_t i = 0; i < len; ++i) 179 { 180 rt ~= formatOne(results[i], format, strAdd, strDelete); 181 } 182 if (rest > 0) 183 { 184 string rest_str = (to!string(rest) ~ "... different remain"); 185 string last_line = format.replace("%pos%", rest_str).replace("%type%", rest_str).replace("%content%", rest_str); 186 rt ~= last_line; 187 } 188 return rt; 189 } 190 }; 191 192 auto strDiff(string a, string b) 193 { 194 alias strDiff = MyersDiff!char; 195 return strDiff.getDiff(a.dup, b.dup); 196 } 197 198 auto strDiffFormat(MyersDiff!(char).DiffResult[] result, string format, string strAdd, string strDelet, size_t max_line = size_t.max) 199 { 200 alias strDiff = MyersDiff!char; 201 return strDiff.formatResult(result, format, strAdd, strDelet, max_line); 202 } 203 204 auto lineDiff(string a, string b) 205 { 206 alias lineDiff = MyersDiff!string; 207 auto array_a = a.replace("\r", "").split("\n"); 208 auto array_b = b.replace("\r", "").split("\n"); 209 return lineDiff.getDiff(array_a, array_b); 210 } 211 212 auto lineDiffFormat(MyersDiff!(string).DiffResult[] result, string format, string strAdd, string strDelet, size_t max_line = size_t.max) 213 { 214 alias lineDiff = MyersDiff!string; 215 return lineDiff.formatResult(result, format, strAdd, strDelet, max_line); 216 } 217